﻿open System
open System.Text
open System.IO

let autoTargetName src =
    let dir = Path.GetDirectoryName(src)
    let name = Path.GetFileNameWithoutExtension(src)
    let ext = Path.GetExtension(src)
    Path.Combine(dir, name + ".projected" + ext)

let parseArgs (args:string[]) =
    if args.Length < 1 then
        printfn "Default usage:"
        printfn "> CsvProject <projection-file> <source-file> <target-file>"
        printfn "Derive target from source file demo.csv -> demo.projected.csv:"
        printfn "> CsvProject <projection-file> <source-file>"
        printfn "Using projection.txt in the current folder as projection-file:"
        printfn "> CsvProject <source-file>"
        None
    else
        let pFile = 
            match args.Length with
                | 1 -> "projection.txt"
                | _ -> args.[0]
        let src =
            match args.Length with
                | 1 -> args.[0]
                | _ -> args.[1]
        let trg = 
            match args.Length with
                | 3 -> args.[2]
                | _ -> autoTargetName src
        printfn "Projection: %s" pFile
        printfn "Source:     %s" src
        printfn "Target:     %s" trg
        Some(pFile, src, trg)

let readProjection file = File.ReadAllLines(file) |> Array.map (fun p -> p.Trim())

let parseLine (l:string) = l.Split(',') |> Array.map (fun e -> e.Trim())

let headAndRows (stream:Stream) =
    let r = new StreamReader(stream)
    let head = if r.EndOfStream then None else Some(r.ReadLine() |> parseLine)
    let rows = seq { while not r.EndOfStream do yield r.ReadLine() |> parseLine }
    (head, rows)

let buildIndex projection head =
    projection |> Array.map (fun p -> head |> Array.tryFindIndex (fun e -> e = p))
    
let runProjection projectionFile sourceFile targetFile =

    let projection = readProjection projectionFile
    
    use trgWr = new StreamWriter(targetFile, false, Encoding.Default, 16 * 1024)
    use srcStr = File.OpenRead(sourceFile)
    
    let write (l:string) =
        trgWr.WriteLine(l)
        1
    
    let head, rows = headAndRows srcStr

    if head.IsNone then
        failwith "The data source has no headline."

    let index = buildIndex projection head.Value
    let reorder (line:string[]) =
        let values = index |> Array.map (fun i -> 
            match i with 
                | Some(iv) -> if line.Length > iv then line.[iv] else String.Empty
                | None -> String.Empty)
        String.Join("," , values)

    write(String.Join(",", projection)) |> ignore
    let cnt = (rows
        |> Seq.map (fun l -> reorder l)
        |> Seq.map write
        |> Seq.sum)

    printfn "Finished with %i data sets." cnt
    0

[<EntryPoint>]
let main args =
    match parseArgs args with
        | Some(pFile, src, trg) -> runProjection pFile src trg
        | None -> 1
